Skip to content

Support dynamic (catch-all) workflows - #774

Open
datashaman wants to merge 1 commit into
temporalio:masterfrom
datashaman:feat-dynamic-workflow
Open

Support dynamic (catch-all) workflows#774
datashaman wants to merge 1 commit into
temporalio:masterfrom
datashaman:feat-dynamic-workflow

Conversation

@datashaman

@datashaman datashaman commented Jul 10, 2026

Copy link
Copy Markdown

What

Adds Dynamic (catch-all) Workflow support — a workflow invoked when the worker receives a type name that is not statically registered. PHP and TypeScript are the only SDKs without it (Go/Java/Python/.NET/Ruby all have it).

  • #[WorkflowMethod(dynamic: true)] — declares the dynamic workflow. The handler reads the real type via Workflow::getInfo() and receives the raw args.
  • WorkflowPrototype::isDynamic() — set by WorkflowReader from the attribute.
  • StartWorkflow — when workflows->find($type) misses, falls back to the registered dynamic prototype (mirrors Python's self._workflows.get(type, self._dynamic_workflow)).
  • GetWorkerInfo — advertises dynamic per workflow, so the RoadRunner temporal plugin can register a Go dynamic-workflow proxy (feature: dynamic (catch-all) workflows roadrunner-temporal#784).
  • WorkflowCollection — enforces at most one dynamic workflow per worker (Go panics, Python raises TypeError; here a LogicException), since a second catch-all would make dispatch ambiguous.

Why

Enables applications that author workflows at runtime (e.g. UI-driven pipeline/automation builders) to give each workflow its own type name — real identity in the Web UI — while one PHP handler interprets it, with no codegen or per-workflow deploy.

Dependencies

End-to-end dispatch requires temporalio/roadrunner-temporal#784, which registers the shared factory proxy via RegisterDynamicWorkflow when this dynamic field is set.

Its Go dependency is now satisfied: temporalio/sdk-go#2449 fixed factory-registered dynamic workflow execution and shipped in go.temporal.io/sdk v1.47.0, which the RoadRunner PR pins.

This PR (the PHP declaration + dispatch) remains self-contained and unit-tested independently of the RoadRunner integration.

Testing

  • New tests/Unit/Declaration coverage, run against both the attribute and annotation readers:
    • testDynamicWorkflow#[WorkflowMethod(dynamic: true)] sets isDynamic().
    • testMultipleDynamicWorkflowsAreRejected — a second dynamic workflow on a worker throws.
  • Full unit suite green (composer run test:unit, 692 tests). composer run cs:diff clean. composer run psalm reports no new issues in the changed files.
  • Validated end-to-end with a custom rr built from roadrunner-temporal#784 and Go SDK v1.47.0: starting an unregistered type (pipeline-blog-publish) is caught by the dynamic handler, which reads the real type via Workflow::getInfo() and completes.

Backwards compatibility

Additive. dynamic defaults to false, so existing workflows and workers behave exactly as before.

@datashaman
datashaman force-pushed the feat-dynamic-workflow branch from 6041218 to 17b1122 Compare July 10, 2026 12:32
Add `#[WorkflowMethod(dynamic: true)]` to declare a dynamic (catch-all)
workflow — invoked when the worker receives a workflow whose type name is
not statically registered. WorkflowReader flags the prototype; StartWorkflow
falls back to the dynamic prototype when no named workflow matches; and the
GetWorkerInfo handshake advertises `dynamic` so the RoadRunner temporal
plugin can register a Go dynamic-workflow proxy for it.

As in the other SDKs (Go panics, Python raises), at most one dynamic workflow
may be registered per worker — WorkflowCollection enforces this.

Enables Dynamic Workflow support in the PHP SDK (PHP and TypeScript are the
only SDKs without it).

End-to-end dispatch also requires temporalio/roadrunner-temporal#784 (register
the dynamic proxy) and temporalio/sdk-go#2449 (execute a factory-registered
dynamic workflow).
@datashaman
datashaman force-pushed the feat-dynamic-workflow branch from 17b1122 to 7eea800 Compare July 30, 2026 12:43
@datashaman
datashaman marked this pull request as ready for review July 30, 2026 12:59
@datashaman
datashaman requested review from a team, roxblnfk and wolfy-j as code owners July 30, 2026 12:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant